﻿using System;
using System.Globalization;
using System.Threading;
using TUtils.BasicHelper.Logging;
using TUtils.Multithreading;
using LogX.Logging;

namespace TUtils.Threads
{
	/// <summary>
	/// Repräsentiert einen Thread, der mit einem benutzerdefinierten Objekt,
	/// einer Threadmethode (Delegate) und einem Handler (Delegate) zur
	/// Verarbeitung von Signalen wie z.B. Exceptions initialisiert werden kann.
	/// Hält einen Thread-Status vor.
	/// </summary>
	/// <remarks>
	/// Reagiert auf die Terminierung der Applikation mittels "ApplicationTerminationEvent".
	/// </remarks>
	/// <typeparam name="T">Typ des benutzerdefierten Typs</typeparam>
	public class TThreadX<T> : IDisposable
	{
		#region types

		/// <summary>
		/// Kontext, auf den die Threadmethode zugreifen kann.
		/// Ermöglicht den Zugriff auf das benutzerdefinierte Objekt
		/// und die Abfrage, ob die Methode terminieren soll aufgrund eines
		/// thread-externen Abbruchs.
		/// </summary>
		public interface IThreadContext
		{
			/// <summary>
			/// true, wen der Thread abgebrochen werden soll.
			/// Die Threadmethode muss auf dieses Signal reagieren.
			/// </summary>
			bool IsCanceling
			{
				get;
			}

			/// <summary>
			/// benutzerdefiertes Objekt, das bei der Initialiserung des Threads
			/// übergeben wurde.
			/// </summary>
			T UserObject
			{
				get;
			}

		}

		/// <summary>
		/// Einfache Implementierung von IThreadContext
		/// </summary>
		private class ThreadContextX : IThreadContext
		{
			private TThreadX<T> _thread;

			public ThreadContextX(TThreadX<T> thread)
			{
				_thread = thread;
			}

			#region IThreadContext Member

			bool IThreadContext.IsCanceling
			{
				get
				{
					return _thread.IsCanceling;
				}
			}

			T IThreadContext.UserObject
			{
				get
				{
					return _thread._obj;
				}
			}

			#endregion


		}

		#endregion

		#region private

		#region private field

		private volatile SignalEnum _status;
		private volatile bool _isCanceling;
		private bool _initialized;
		public bool IsCanceling
		{
			get
			{
				return _isCanceling;
			}
		}
		private Action<IThreadContext, SignalEnum, Exception> _signalHandler;
		private Thread _thread;
		private Action<IThreadContext> _threadMethod;
		private T _obj;
		public string ThreadName
		{
			get;
			private set;
		}
		private ThreadContextX _threadContext;
		#endregion

		#region private methods

		private void InternalThreadMethod()
		{
			try
			{
				ApplicationTerminationEvent.ApplicationIsTerminating += ApplicationTerminationEventApplicationIsTerminating;
				ChangeStatus(SignalEnum.STARTED, null);
				_threadMethod(_threadContext);
				if (IsCanceling)
					ChangeStatus(SignalEnum.STOPPED_CANCELED, null);
				else
					ChangeStatus(SignalEnum.STOPPED_NORMAL, null);
			}
#			if !THROWEXCEPTION
			catch (Exception e)
			{
				ChangeStatus(SignalEnum.STOPPED_EXCEPTION, e);

				TLog.Instance.LogException(e);
			}
#			endif
			finally
			{
				ApplicationTerminationEvent.ApplicationIsTerminating -= ApplicationTerminationEventApplicationIsTerminating;
				OnFinallyLeaving();
			}
		}

		void ApplicationTerminationEventApplicationIsTerminating()
		{
			CancelAsync();
		}

		/// <summary>
		/// Setzt den neuen Thread-Status und ruft den Signal-Handler auf.
		/// </summary>
		/// <param name="newStatus"></param>
		/// <param name="e"></param>
		private void ChangeStatus(SignalEnum newStatus, Exception e)
		{
			if (newStatus != SignalEnum.STARTING)
			{
				if (ThreadName == "ElevatorServiceHandler")
				{
					int a = 0;
				}
				TLog.Instance.LogInfo(this,
					"status of thread {0} changed from {1} to {2}",
					() => ThreadName,
					() => _status,
					() => newStatus);
			}

			if ( _status == SignalEnum.STARTED && newStatus == SignalEnum.STARTED)
				throw new ApplicationException("hjfd892rlwhj");

			_status = newStatus;

			var signalHandler = _signalHandler;
			if (signalHandler != null)
				signalHandler(_threadContext, newStatus, e);
		}

		#endregion

		#endregion

		#region protected

		/// <summary>
		/// Startet Thread asynchron.
		/// Nochmaliger Aufruf eines schon gestarteten Threads wird ohne Fehler ignoriert.
		/// Derzeit nicht multithread-sicher: Die StartAsync() Methode einer
		/// TThread-Instanz sollte also nicht gleichzeitig von verschiedenen Threads 
		/// aus aufgerufen werden.
		/// </summary>
		public virtual void StartAsync()
		{
			switch (_status)
			{
				case SignalEnum.STARTING:
				case SignalEnum.STARTED:
					return;
				case SignalEnum.INITIALIZED:
				case SignalEnum.STOPPED_NORMAL:
				case SignalEnum.STOPPED_CANCELED:
				case SignalEnum.STOPPED_EXCEPTION:
					break;
				default:
					throw new NotImplementedException("hjsdkjvsaie87rz " + _status);
			}

			ChangeStatus(SignalEnum.STARTING, null);

			_thread = new Thread(InternalThreadMethod);
			_thread.Name = ThreadName + " " + Guid.NewGuid();
			_thread.Start();
		}

		/// <summary>
		/// Diese Methode wird vom Thread als allerletztes aufgerufen, bevor der Thread schließlich
		/// terminiert. Diese Methode wird garantiert aufgerufen.
		/// </summary>
		protected virtual void OnFinallyLeaving()
		{

		}

		#endregion

		#region constructor

		/// <summary>
		/// Initialisiert den Thread vollständig
		/// </summary>
		/// <param name="signalHandler">
		/// dieser Delegate wird aufgerufen wenn 
		/// in der Thread-Methode eine Exception ausgelöst wurde oder
		/// sich der Threadstatus ändert.
		/// <code><![CDATA[void Handler(IThreadContext context, SignalEnum threadStatus, Exception e)]]></code>
		/// </param>
		/// <param name="threadMethod">
		/// die Threadmethode
		/// </param>
		/// <param name="obj">
		/// Benutzerdefiertes Objekt, auf das die Threadmethode Zugriff erhält.
		/// </param>
		/// <param name="threadName"></param>
		/// <returns>
		/// Diese Instanz
		/// </returns>
		public TThreadX<T> Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			Action<IThreadContext> threadMethod,
			T obj,
			string threadName)
		{
			if (_initialized)
				throw new ApplicationException("hashfskdvnazu63kjdg allready initialized " + threadName);

			_initialized = true;
			_isCanceling = false;
			ThreadName = threadName;
			_threadContext = new ThreadContextX(this);
			_threadMethod = threadMethod;
			_obj = obj;
			_signalHandler = signalHandler;
			_status = SignalEnum.INITIALIZED;
			return this;
		}

		#endregion

		#region public

		#region public properties

		/// <summary>
		/// rised, when status changed to CANCELING
		/// </summary>
		public event Action Canceling = () =>
		{
		};

		/// <summary>
		/// Der Status des Threads
		/// </summary>
		public SignalEnum ThreadStatus
		{
			get
			{
				return _status;
			}
		}

		/// <summary>
		/// True, wenn der Thread nicht mehr läuft.
		/// </summary>
		public bool Stopped
		{
			get
			{
				switch (ThreadStatus)
				{
					case SignalEnum.INITIALIZED:
					case SignalEnum.STARTING:
					case SignalEnum.STARTED:
						return false;
					case SignalEnum.STOPPED_NORMAL:
					case SignalEnum.STOPPED_CANCELED:
					case SignalEnum.STOPPED_EXCEPTION:
						return true;
					default:
						throw new NotImplementedException("87635903jfds " + ThreadStatus);
				}
			}
		}

		/// <summary>
		/// True, wenn der Thread nicht mehr läuft.
		/// </summary>
		public bool Running
		{
			get
			{
				switch (ThreadStatus)
				{
					case SignalEnum.STARTING:
					case SignalEnum.STARTED:
						return true;
					case SignalEnum.INITIALIZED:
					case SignalEnum.STOPPED_NORMAL:
					case SignalEnum.STOPPED_CANCELED:
					case SignalEnum.STOPPED_EXCEPTION:
						return false;
					default:
						throw new NotImplementedException("87635903jfds " + ThreadStatus);
				}
			}
		}

		public enum SignalEnum
		{
			INITIALIZED,
			STARTING,
			STARTED,
			STOPPED_NORMAL,
			STOPPED_CANCELED,
			STOPPED_EXCEPTION
		}

		#endregion

		#region public methods

		/// <summary>
		/// Stoppt den Thread asynchron.
		/// Die Threadmethode muss auf das Signal IThreadContext.IsCanceling
		/// reagieren.
		/// </summary>
		public void CancelAsync()
		{
			_isCanceling = true;
			Canceling();
		}

		public void Dispose()
		{
			CancelAsync();
		}


		#endregion

		#endregion

	}

	/// <summary>
	/// Leere Klasse
	/// </summary>
	public class TThreadX_Dummy
	{
	}

	/// <summary>
	/// Thread, der kein benutzerdefiniertes Objekt benötigt.
	/// </summary>
	public class TThreadX : TThreadX<TThreadX_Dummy>
	{
		/// <summary>
		/// DEPRECATED
		/// </summary>
		private new TThreadX<TThreadX_Dummy> Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			Action<IThreadContext> threadMethod,
			TThreadX_Dummy obj,
			string threadName)
		{
			throw new ApplicationException("67dhjfsdlksahc89werifh");
		}

		/// <summary>
		/// Initialisiert den Thread vollständig
		/// </summary>
		/// <param name="signalHandler">
		/// dieser Delegate wird aufgerufen wenn 
		/// in der Thread-Methode eine Exception ausgelöst wurde oder
		/// sich der Threadstatus ändert.
		/// <code><![CDATA[void Handler(IThreadContext context, SignalEnum threadStatus, Exception e)]]></code>
		/// </param>
		/// <param name="threadMethod">
		/// die Threadmethode
		/// </param>
		/// <param name="threadName"></param>
		/// <returns>
		/// Diese Instanz
		/// </returns>
		public TThreadX Init(
			Action<IThreadContext, SignalEnum, Exception> signalHandler,
			Action<IThreadContext> threadMethod,
			string threadName)
		{
			base.Init(
				signalHandler,
				threadMethod,
				new TThreadX_Dummy(),
				threadName);

			return this;
		}


	}

}
